package com.zyb.lmbackend.service.impl;

/**
 * 类目归纳服务实现。
 *
 * 流程：
 * 1) 从 goods_back 随机抽样并清洗去重
 * 2) 调用 LLM 归纳类目（限制最多 15 条）并落库/激活
 * 3) 对样本做一次分类评估（OTHER/低置信）估算可分类度
 * 4) 对难分类样本再归纳，作为预览候选返回（不入库）
 */
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zyb.lmbackend.entity.CategoryDict;
import com.zyb.lmbackend.llm.LlmClient;
import com.zyb.lmbackend.llm.model.CategoryDef;
import com.zyb.lmbackend.llm.model.ClsResult;
import com.zyb.lmbackend.mapper.GoodsBackMapper;
import com.zyb.lmbackend.service.CategoryDictService;
import com.zyb.lmbackend.service.TaxonomyBuilderService;
import com.zyb.lmbackend.util.TextSanitizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Slf4j
public class TaxonomyBuilderServiceImpl implements TaxonomyBuilderService {

    private final GoodsBackMapper goodsBackMapper;
    private final LlmClient llmClient;
    private final CategoryDictService categoryDictService;
    private final ObjectMapper objectMapper;

    @Override
    @Transactional
    /**
     * 执行类目归纳主流程。
     * @param sampleSizeParam 样本数量（可为空则按总量的 15% 且下限 50，上限 2000）
     */
    public BuildResult build(Integer sampleSizeParam) {
        long total = goodsBackMapper.countAll();
//        一次最多取出50条
        int defaultSample = (int) Math.max(50, Math.floor(total * 0.15));
        int sampleSize = Math.min(2000, sampleSizeParam != null ? sampleSizeParam : defaultSample);
        if (sampleSize <= 0) sampleSize = 50;
        // 取样并清洗去重
        List<String> raw = goodsBackMapper.selectRandomBackDetails(sampleSize);
//        压缩无意义的部分，节省token
        List<String> samples = TextSanitizer.sanitizeAndDedupe(raw);
        if (samples.isEmpty()) {
            return new BuildResult(0, List.of());
        }

        // 1) 归纳类目（LLM 输出 JSON 数组 -> CategoryDef 列表）
        List<CategoryDef> defs = llmClient.buildTaxonomy(samples);
        // 保守：5-15 之间
        if (defs.size() > 15) defs = defs.subList(0, 15);

        // 2) Upsert 到 category_dict（存在更新，不存在新增），统一激活
        int inserted = upsertCategoryDict(defs);

        // 3) 评估样本可分类度（OTHER / 低置信）
        List<String> codes = defs.stream().map(d -> d.getCode()).collect(Collectors.toList());
        List<ClsResult> cls;
        try {
            cls = codes.isEmpty() ? List.of() : llmClient.classifyBatch(samples, codes);
        } catch (Exception e) {
            // 不因评估失败而中断归纳流程，视为全部可分类
            log.warn("Sample classify for evaluation failed, continue without preview: {}", e.getMessage());
            cls = List.of();
        }
        long hard = cls.stream().filter(r -> "OTHER".equalsIgnoreCase(r.getCategoryCode()) || r.getConfidence() < 0.7).count();
        double ratio = samples.isEmpty() ? 0 : (hard * 1.0 / samples.size());

        // 4) 新类目候选：若 >5%，对这些样本再归纳，作为预览返回（不入库）
        List<CategoryDef> preview = new ArrayList<>();
        if (ratio > 0.05) {
            List<String> hardSamples = new ArrayList<>();
            for (ClsResult r : cls) {
                if (r.getConfidence() < 0.7 || "OTHER".equalsIgnoreCase(r.getCategoryCode())) {
                    int idx = r.getIndex();
                    if (idx >= 0 && idx < samples.size()) hardSamples.add(samples.get(idx));
                }
            }
            hardSamples = TextSanitizer.sanitizeAndDedupe(hardSamples);
            if (!hardSamples.isEmpty()) {
                // 对难样本再次归纳，作为预览候选类目，不入库
                List<CategoryDef> candidates = llmClient.buildTaxonomy(hardSamples);
                if (candidates.size() > 15) candidates = candidates.subList(0, 15);
                preview.addAll(candidates);
            }
        }

        return new BuildResult(inserted, preview);
    }

    private int upsertCategoryDict(List<CategoryDef> defs) {
        // 将归纳出的类目写入/更新到 category_dict，并置为启用
        int inserted = 0;
        for (CategoryDef d : defs) {
            if (d.getCode() == null || d.getName() == null) continue;
            String code = d.getCode().trim().toUpperCase(Locale.ROOT);
            CategoryDict exist = categoryDictService.getOne(new LambdaQueryWrapper<CategoryDict>().eq(CategoryDict::getCode, code));
            String keywordsJson = toJsonArray(d.getKeywords());
            if (exist == null) {
                CategoryDict e = new CategoryDict();
                e.setCode(code);
                e.setName(d.getName());
                e.setDefinition(d.getDefinition());
                e.setKeywords(keywordsJson);
                e.setIsActive((byte) 1);
                categoryDictService.save(e);
                inserted++;
            } else {
                exist.setName(d.getName());
                exist.setDefinition(d.getDefinition());
                exist.setKeywords(keywordsJson);
                exist.setIsActive((byte) 1);
                categoryDictService.updateById(exist);
            }
        }
        return inserted;
    }

    private String toJsonArray(List<String> keywords) {
        // 关键词序列化失败时兜底为空数组字符串
        if (keywords == null) return "[]";
        try {
            return objectMapper.writeValueAsString(keywords);
        } catch (JsonProcessingException e) {
            return "[]";
        }
    }
}
